﻿using System;
using System.Collections.Generic;

namespace BSharp.Compiler
{
	public class BParser
	{
		public BParser (BSharpContext context)
		{
		}

		public AstNode Parse (TokenStream stream)
		{
			CodeBlock root = new CodeBlock (stream.Location);

			while (!stream.EndOfStream) {
				if (stream.Match (TokenClass.Identifier, TokenClass.OpenParan)) {
					root.AddItem (ParseFunctionDeclaration (stream));
				}
			}

			return root;
		}

		private AstNode ParseFunctionDeclaration (TokenStream stream)
		{
			string name = stream.Expect (TokenClass.Identifier).Value;

			SourceLocation declLocation = stream.Location;

			stream.Expect (TokenClass.OpenParan);

			List<string> paramList = new List<string> ();

			while (!stream.Accept (TokenClass.CloseParan)) {
				Token identToken = stream.Expect (TokenClass.Identifier);

				if (identToken != null) {
					paramList.Add (identToken.Value);
				}

				if (!stream.Match (TokenClass.CloseParan)) {
					stream.Expect (TokenClass.Comma);
				}
			}
			FunctionDeclaration declaration = new FunctionDeclaration (declLocation, name, ParseCodeBlock (stream));
			declaration.Parameters.AddRange (paramList);

			return declaration;
		}

		private AstNode ParseCodeBlock (TokenStream stream)
		{
			CodeBlock codeBlock = new CodeBlock (stream.Location);

			stream.Expect (TokenClass.OpenBrace);


			while (!stream.Accept (TokenClass.CloseBrace)) {
				codeBlock.AddItem (ParseStatement (stream));
			}

			return codeBlock;
		}

		private AstNode ParseStatement (TokenStream stream) 
		{
			Token current = stream.Current;

			if (current.Class == TokenClass.Keyword) {
				AstNode ret = null;
				switch (current.Value) {
				case "auto":
					ret = ParseAutoDeclaration (stream);
					stream.Expect (TokenClass.SemiColon);
					return ret;
				case "if":
					return ParseIfStatement (stream);
				}

				stream.Expect (TokenClass.SemiColon);

				return ret;
			} else if (current.Class == TokenClass.OpenBrace) {
				return ParseCodeBlock (stream);
			} 

			return ParseExpressionStatement (stream);;
		}

		private AstNode ParseIfStatement (TokenStream stream)
		{
			stream.Expect (TokenClass.Keyword, "if");
			stream.Expect (TokenClass.OpenParan);
			AstNode condition = ParseExpression (stream);
			stream.Expect (TokenClass.CloseParan);
			AstNode body = ParseStatement (stream);
			AstNode elseBody = null;
			if (stream.Accept (TokenClass.Keyword, "else")) {
				elseBody = ParseStatement (stream);
			}
			return new IfStatement (condition.Location, condition, body, elseBody);
		}

		private AstNode ParseAutoDeclaration (TokenStream stream)
		{
			SourceLocation location = stream.Location;

			stream.Expect (TokenClass.Keyword, "auto");

			string identifier = stream.Expect (TokenClass.Identifier).Value;


			if (stream.Accept (TokenClass.OpenBracket)) {

				Token size = stream.Expect (TokenClass.IntLiteral);

				stream.Expect (TokenClass.CloseBracket);

				return new VectorDeclaration (location, identifier, UInt16.Parse (size.Value));
			}

			if (stream.Match (TokenClass.SemiColon)) {
				return new AutoDeclaration (location, identifier, null);
			}

			stream.Expect (TokenClass.Operator, "=");

			return new AutoDeclaration (location, identifier, ParseExpression (stream));

		}

		private AstNode ParseExpressionStatement (TokenStream stream)
		{
			AstNode expression = ParseExpression (stream);

			if (expression is BinaryExpression) {
				BinaryExpression binexp = expression as BinaryExpression;
				if (binexp.Operation != BinaryOperation.Assign) {

				}
			} else if (!(expression is CallExpression)) {
				
			}


			stream.Expect (TokenClass.SemiColon);
			return new ExpressionStatement (expression.Location, expression);
		}

		private AstNode ParseExpression (TokenStream stream)
		{
			return ParseAssign (stream);
		}

		private AstNode ParseAssign (TokenStream stream)
		{
			AstNode expr = ParseOr (stream);
			while (stream.Match (TokenClass.Operator)) {
				switch (stream.Current.Value) {
				case "=":
					stream.Accept (TokenClass.Operator);
					expr = new BinaryExpression (stream.Location,
						BinaryOperation.Assign,
						expr,
						ParseOr (stream)
					);
					continue;
				default:
					break;
				}
				break;
			}
			return expr;
		}

		private AstNode ParseOr (TokenStream stream)
		{
			AstNode expr = ParseXor (stream);
			while (stream.Match (TokenClass.Operator)) {
				switch (stream.Current.Value) {
				case "|":
					stream.Accept (TokenClass.Operator);
					expr = new BinaryExpression (stream.Location,
						BinaryOperation.Or,
						expr,
						ParseXor (stream)
					);
					continue;
				default:
					break;
				}
				break;
			}
			return expr;
		}

		private AstNode ParseXor (TokenStream stream)
		{
			AstNode expr = ParseAnd (stream);
			while (stream.Match (TokenClass.Operator)) {
				switch (stream.Current.Value) {
				case "^":
					stream.Accept (TokenClass.Operator);
					expr = new BinaryExpression (stream.Location,
						BinaryOperation.Xor,
						expr,
						ParseAnd (stream)
					);
					continue;
				default:
					break;
				}
				break;
			}
			return expr;
		}

		private AstNode ParseAnd (TokenStream stream)
		{
			AstNode expr = ParseEquality (stream);
			while (stream.Match (TokenClass.Operator)) {
				switch (stream.Current.Value) {
				case "&":
					stream.Accept (TokenClass.Operator);
					expr = new BinaryExpression (stream.Location,
						BinaryOperation.And,
						expr,
						ParseEquality (stream)
					);
					continue;
				default:
					break;
				}
				break;
			}
			return expr;
		}

		private AstNode ParseEquality (TokenStream stream)
		{
			AstNode expr = ParseRelational (stream);
			while (stream.Match (TokenClass.Operator)) {
				switch (stream.Current.Value) {
				case "==":
					stream.Accept (TokenClass.Operator);
					expr = new BinaryExpression (stream.Location,
						BinaryOperation.Equal,
						expr,
						ParseRelational (stream)
					);
					continue;
				case "!=":
					stream.Accept (TokenClass.Operator);
					expr = new BinaryExpression (stream.Location,
						BinaryOperation.NotEqual,
						expr,
						ParseRelational (stream)
					);
					continue;
				default:
					break;
				}
				break;
			}
			return expr;
		}

		private AstNode ParseRelational (TokenStream stream)
		{
			AstNode expr = ParseBitshift (stream);
			while (stream.Match (TokenClass.Operator)) {
				switch (stream.Current.Value) {
				case "<":
					stream.Accept (TokenClass.Operator);
					expr = new BinaryExpression (stream.Location,
						BinaryOperation.LessThan,
						expr,
						ParseBitshift (stream)
					);
					continue;
				case "<=":
					stream.Accept (TokenClass.Operator);
					expr = new BinaryExpression (stream.Location,
						BinaryOperation.LessThanOrEqu,
						expr,
						ParseBitshift (stream)
					);
					continue;
				case ">":
					stream.Accept (TokenClass.Operator);
					expr = new BinaryExpression (stream.Location,
						BinaryOperation.GreaterThan,
						expr,
						ParseBitshift (stream)
					);
					continue;
				case ">=":
					stream.Accept (TokenClass.Operator);
					expr = new BinaryExpression (stream.Location,
						BinaryOperation.GreaterThanOrEqu,
						expr,
						ParseBitshift (stream)
					);
					continue;
				default:
					break;
				}
				break;
			}
			return expr;
		}

		private AstNode ParseBitshift (TokenStream stream)
		{
			AstNode expr = ParseMultiplicativeExpression (stream);
			while (stream.Match (TokenClass.Operator)) {
				switch (stream.Current.Value) {
				case "<<":
					stream.Accept (TokenClass.Operator);
					expr = new BinaryExpression (stream.Location, BinaryOperation.LeftBitShift, expr,
						ParseMultiplicativeExpression (stream));
					continue;
				case ">>":
					stream.Accept (TokenClass.Operator);
					expr = new BinaryExpression (stream.Location, BinaryOperation.RightBitShift, expr,
						ParseMultiplicativeExpression (stream));
					continue;
				default:
					break;
				}
				break;
			}
			return expr;
		}

		private AstNode ParseAdditiveExpression (TokenStream stream)
		{
			AstNode expr = ParseMultiplicativeExpression (stream);
			while (stream.Match (TokenClass.Operator)) {
				switch (stream.Current.Value) {
				case "+":
					stream.Accept (TokenClass.Operator);
					expr = new BinaryExpression (stream.Location, BinaryOperation.Add, expr,
						ParseMultiplicativeExpression (stream));
					continue;
				case "-":
					stream.Accept (TokenClass.Operator);
					expr = new BinaryExpression (stream.Location, BinaryOperation.Sub, expr,
						ParseMultiplicativeExpression (stream));
					continue;
				default:
					break;
				}
				break;
			}
			return expr;
		}

		public AstNode ParseMultiplicativeExpression (TokenStream stream)
		{
			AstNode expr = ParseUnary (stream);
			while (stream.Match (TokenClass.Operator)) {
				switch (stream.Current.Value) {
				case "*":
					stream.Accept (TokenClass.Operator);
					expr = new BinaryExpression (stream.Location, BinaryOperation.Mul, expr,
						ParseUnary (stream));
					continue;
				case "/":
					stream.Accept (TokenClass.Operator);
					expr = new BinaryExpression (stream.Location, BinaryOperation.Div, expr,
						ParseUnary (stream));
					continue;
				case "%":
					stream.Accept (TokenClass.Operator);
					expr = new BinaryExpression (stream.Location, BinaryOperation.Mod, expr,
						ParseUnary (stream));
					continue;
				default:
					break;
				}
				break;
			}
			return expr;
		}

		public AstNode ParseUnary (TokenStream stream)
		{
			if (stream.Match (TokenClass.Operator)) {
				switch (stream.Current.Value) {
				case "-":
					stream.Accept (TokenClass.Operator);
					return new UnaryExpression (stream.Location, UnaryOperation.Negate, ParseUnary (
						stream));
				case "~":
					stream.Accept (TokenClass.Operator);
					return new UnaryExpression (stream.Location, UnaryOperation.BitwiseNot, ParseUnary (
						stream));
				case "!":
					stream.Accept (TokenClass.Operator);
					return new UnaryExpression (stream.Location, UnaryOperation.Not, ParseUnary (
						stream));
				case "*":
					stream.Accept (TokenClass.Operator);
					return new UnaryExpression (stream.Location, UnaryOperation.Derefence, ParseUnary (
						stream));
				case "&":
					stream.Accept (TokenClass.Operator);
					return new UnaryExpression (stream.Location, UnaryOperation.Reference, ParseUnary (
						stream));

				}
			}
			return ParseCallSubscriptAccess (stream);
		}

		public AstNode ParseCallSubscriptAccess (TokenStream stream)
		{
			return ParseCallSubscriptAccess (stream, ParseTerminal (stream));
		}

		public AstNode ParseCallSubscriptAccess (TokenStream stream, AstNode lvalue)
		{
			if (stream.Match (TokenClass.OpenParan)) {
				return ParseCallSubscriptAccess (stream, new CallExpression (stream.Location, lvalue,
					ParseArgumentList (stream)));
			} else if (stream.Match (TokenClass.OpenBracket)) {
				return ParseCallSubscriptAccess (stream, ParseIndexer (lvalue, stream));
			}
			return lvalue;
		}

		private AstNode ParseIndexer (AstNode lvalue, TokenStream stream)
		{
			stream.Expect (TokenClass.OpenBracket);
			AstNode index = ParseExpression (stream);
			stream.Expect (TokenClass.CloseBracket);
			return new IndexerExpression (stream.Location, lvalue, index);
		}

		private AstNode ParseArgumentList (TokenStream stream)
		{
			stream.Expect (TokenClass.OpenParan);

			ArgumentList list = new ArgumentList (stream.Current.Location);

			while (!stream.Match (TokenClass.CloseParan)) {
				list.Add (ParseExpression (stream));

				if (!stream.Match (TokenClass.CloseParan)) {

					stream.Expect (TokenClass.Comma);
				}
			}

			stream.Expect (TokenClass.CloseParan);

			return list;
		}

		private AstNode ParseTerminal (TokenStream stream)
		{
			Token current = stream.Current;

			switch (current.Class) {
			case TokenClass.Identifier:
				stream.ReadToken ();
				return new IdentifierExpression (current.Location, current.Value);
			case TokenClass.IntLiteral:
				stream.ReadToken ();
				return new ConstantExpression (current.Location, Int32.Parse (current.Value));
			}

			return null;
		}
	}
}

